/**
 * DownloadManager.java
 * @author: liuex
 * 2007-12-27 ����01:18:03
 *
 * note: 
 */
package lex.mtd.core;

import java.io.IOException;
import java.util.logging.Logger;

import org.lex.utils.Callback;

import lex.mtd.core.net.ContentLengthFetcher;
import lex.mtd.core.net.DataSource;
import lex.mtd.core.net.DataSourceObserver;
import lex.mtd.core.utils.DaemonExecutor;
import lex.mtd.core.utils.LogSet;
import lex.mtd.core.utils.NullLogSet;
import lex.mtd.utils.Logs;

public class DownloadManager implements DownloadThreadObserver, DataSourceObserver
{
	private DownloadManagerConfig config = DownloadManagerConfig.DEFAULT.clone();
	private DownloadObject task = null;
	private FileBlockSet fileSet = null;
	private DataSourceSet dataSet = null;
	private DownloadThreadSet threadSet = null;
	private boolean running = false;
	// ��¼Connector,��"ֹͣ"����ʱ��Ҫʹ��
	// private Hashtable<FtpURL, Future<?>> connectors = null;
	private transient Logger debug = Logs.get( DownloadManager.class );

	public DownloadManager( DownloadObject task, LogSet logSet )
	{
		this.task = task;
		this.fileSet = new FileBlockSet( task.getDirectory(), task.getFileName(),
			config.minimalDataBlock );
		this.dataSet = new DataSourceSet( task.getURLSet(), this, logSet );
		this.threadSet = new DownloadThreadSet( this, this.fileSet );
		// this.connectors = new Hashtable<FtpURL, Future<?>>(
		// task.getAddressCount() );
	}

	public void start()
	{
		if( true == running )
			return;
		else
		{
			this.running = true;
			// get contenet length
			new ContentLengthFetcher( task, new NullLogSet(), new Callback<Long>(){
				@Override
				public void receive( Long value )
				{
					fileSet.verifyContentLength( value );
					dataSet.start();
				}
			} );
		}
	}

	public void stop()
	{
		this.running = false;
		debug.fine( "not implement yet" );
	}

	public boolean isRunning()
	{
		return this.running;
	}

	public DownloadObject getDownloadTask()
	{
		return this.task;
	}

	public String getFileName()
	{
		return this.task.getFileName();
	}

	public long getContentLength()
	{
		return this.fileSet.getContentLength();
	}

	public DownloadThreadSet getThreadSet()
	{
		return threadSet;
	}

	public FileBlockSet getFileSet()
	{
		return fileSet;
	}

	/***************************************************************************
	 * DataSourceObserver
	 **************************************************************************/
	public synchronized void dataSourceAvailable( final DataSource source )
	{
		debug.fine( source.getURL() );
		this.checkThreadCountLimit();
		DaemonExecutor.execute( new Runnable(){
			@Override
			public void run()
			{
				FileBlock file = fileSet.splitMaxRemainLengthBlock();
				if( null == file )
					return;
				else
					threadSet.addDownloadThread( source, file );
			}
		} );
	}

	/***************************************************************************
	 * DownloadThreadObserver
	 **************************************************************************/
	@Override
	public synchronized void threadError( DownloadThread thread )
	{
		System.out.println( "ERROR:" );
		System.out.print( thread.toString() );
		thread.getDataSource().close();
		this.checkThreadCountLimit();
	}

	@Override
	public synchronized void threadFinished( DownloadThread thread )
	{
		debug.fine( thread.getFileBlock().getFileName() + " finished" );
		this.checkThreadCountLimit();
		try
		{
			this.fileSet.merge();
		}
		catch( IOException e )
		{
			e.printStackTrace();
		}
		// this.tryReuseDataSource( thread.getDataSource() );
	}

	private void checkThreadCountLimit()
	{
		if( (threadSet.getRunningThreadCount() > config.maximalThreadCount)
				|| (fileSet.getMaxRemainLength() < config.minimalDataBlock) )
			dataSet.stop();
		else
		{
			dataSet.start();
		}
	}

	void tryReuseDataSource( final DataSource ds )
	{
		// Ŀ��������ģ�����һ������������Ժ󣬼����Ŀ���Ҫ��ɵ�ʱ��
		// ���糬��5���ӵĻ�������ʹ������ɿ��DataSource������һ���µ�
		// Part������ʼ���䣬������ܵĴ����ٶ�.
		DownloadThread longest = this.threadSet.findLongestRemainTimePart();
		long time = longest.getRemainTime();
		debug.fine( "longest part: " + time + " s" );
		if( time > this.config.minimalTimeBlock )
		{
			longest = this.threadSet.findLongestRemainLengthPart();
			long length = longest.getFileBlock().getLength() - longest.getTransferredBytes();
			debug.fine( "longest part: " + length + " bytes" );
			if( length < this.config.minimalDataBlock )
			{
				debug.fine( "task completing, not reuse source: " + ds.getURL() );
				return;
			}
		}
		DaemonExecutor.execute( new Runnable(){
			@Override
			public void run()
			{
				dataSourceAvailable( ds );
			}
		} );
	}
}
